﻿/* Based on code from http://www.jeremyskinner.co.uk/2010/09/18/replacing-expression-compile-under-windows-phone-7/ */

using System;
using System.Linq.Expressions;
using System.Reflection;

namespace Outcoder.Reflection
{
	public static class ExpressionExtensions
	{
		static string notPublicErrorMessage
			= "May only be used with expressions that reference public properties.";

		public static TDelegate Compile<TDelegate>(
			this Expression<TDelegate> expression)
		{
			object compiledDelegate = CompilePropertyGetterExpression(
											expression, typeof(TDelegate));
			return (TDelegate)compiledDelegate;
		}

		public static TDelegate Compile<TDelegate>(
			this Expression<TDelegate> expression, object owner)
		{
			object compiledDelegate = CompilePropertyGetterExpression(
											expression, typeof(TDelegate), owner);
			return (TDelegate)compiledDelegate;
		}

		public static Action<TProperty> CompileSetter<TDelegate, TProperty>(
			this Expression<TDelegate> expression, object owner)
		{
			object compiledDelegate
				= CompilePropertySetterExpression(expression, owner);

			return (Action<TProperty>)compiledDelegate;
		}

		public static PropertyCompileResult<TProperty> Compile2<TProperty, TDelegate>(
			this Expression<TDelegate> expression, object owner)
		{
			PropertyInfo member = GetPropertyFromExpression(expression);

			if (member == null)
			{
				throw new NotSupportedException(notPublicErrorMessage);
			}

			Type getterType = typeof(TDelegate);
			Delegate getter = Delegate.CreateDelegate(
									getterType, owner, member.GetGetMethod());

			var propertyType = member.PropertyType;

			var setterType = Expression.GetActionType(new[] { propertyType });

			Delegate setter = Delegate.CreateDelegate(
									setterType, owner, member.GetSetMethod());

			return new PropertyCompileResult<TProperty>(
				(Func<TProperty>)getter, (Action<TProperty>)setter);
		}

		static object CompilePropertyGetterExpression(
			LambdaExpression expression, Type delegateType, object owner)
		{
			PropertyInfo member = GetPropertyFromExpression(expression);

			if (member == null)
			{
				throw new NotSupportedException(notPublicErrorMessage);
			}

			Delegate compiledDelegate = Delegate.CreateDelegate(
								delegateType, owner, member.GetGetMethod());

			return compiledDelegate;
		}

		static object CompilePropertyGetterExpression(
			LambdaExpression expression, Type delegateType)
		{
			PropertyInfo member = GetPropertyFromExpression(expression);

			if (member == null)
			{
				throw new NotSupportedException(notPublicErrorMessage);
			}

			Delegate compiledDelegate
				= Delegate.CreateDelegate(delegateType, member.GetGetMethod());
			return compiledDelegate;
		}

		static object CompilePropertySetterExpression(
			LambdaExpression expression, object owner)
		{
			PropertyInfo member = GetPropertyFromExpression(expression);

			if (member == null)
			{
				throw new NotSupportedException(notPublicErrorMessage);
			}

			var propertyType = member.PropertyType;
			var setterType = Expression.GetActionType(new[] { propertyType });

			Delegate compiledDelegate
				= Delegate.CreateDelegate(setterType, owner, member.GetSetMethod());

			return compiledDelegate;
		}

		static PropertyInfo GetPropertyFromExpression(LambdaExpression expression)
		{
			MemberExpression memberExp = UnwrapUnary(expression.Body);

			if (memberExp == null)
			{
				return null;
			}

			return memberExp.Member as PropertyInfo;
		}

		static MemberExpression UnwrapUnary(Expression toUnwrap)
		{
			var unaryExpression = toUnwrap as UnaryExpression;
			if (unaryExpression != null)
			{
				return unaryExpression.Operand as MemberExpression;
			}

			return toUnwrap as MemberExpression;
		}
	}

	public class PropertyCompileResult<TProperty>
	{
		public Func<TProperty> Getter { get; private set; }
		public Action<TProperty> Setter { get; private set; }

		public PropertyCompileResult(Func<TProperty> getter, Action<TProperty> setter)
		{
			Getter = getter;
			Setter = setter;
		}
	}
}
